﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace AntBot
{
	public class State
	{
		public const int MY_ANT = 1;
		public const int NO_ANT = 0;

		public const int NDIRECTIONS = 4;
		public static readonly int[,] DIRECTIONS = { {0, -1}, {1, 0}, {0, 1}, {-1, 0} }; // N, E, S, W
		public static readonly char[] CDIRECTIONS = {'N', 'E', 'S', 'W'};
		public enum Directions
		{
			Unknown = -1,
			North = 0,
			East = 1,
			South = 2,
			West = 3
		}

		public Torus Map;
		public Router Router;

		//public Settings Settings { get { return Config.GameSettings; } private set { Config.GameSettings = value; } }

		public int TurnNumber { get; set; }
		public SortedLocList MyAnts;
		public SortedLocList Enemies;
		public SortedLocList Food;
		public SortedLocList Water;

		public State(Settings settings)
		{
			Config.GameSettings = settings;
			Map = new Torus(settings.Cols, settings.Rows);
			MyAnts = new SortedLocList(settings.Cols * settings.Rows / 10);
			Enemies = new SortedLocList(settings.Cols * settings.Rows / 10);
			Food = new SortedLocList(settings.Cols * settings.Rows / 10);
			Water = new SortedLocList(settings.Cols * settings.Rows / 10);
			Router = new Router(settings.Cols, settings.Rows);
		}

		//returns the new location from moving in a given direction
		public Location GetNewLocation(Location curLocation, Directions direction)
		{
			return GetNewLocation(curLocation, direction, Map.Cols, Map.Rows);
		}

		public static Location GetNewLocation(Location curLocation, Directions direction, int cols, int rows)
		{
			return new Location(
				Torus.Adjust(curLocation.Col + DIRECTIONS[(int)direction, 0], cols),
				Torus.Adjust(curLocation.Row + DIRECTIONS[(int)direction, 1], rows));
		}

		//determine the 1 or 2 fastest (closest) directions to reach a location
		public Directions GetDirection(Location fromLoc, Location toLoc)
		{
			return GetDirection(fromLoc, toLoc, Map.Cols, Map.Rows);
		}

		public static Directions GetDirection(Location fromLoc, Location toLoc, int cols, int rows)
		{
			fromLoc = Torus.Adjust(fromLoc, cols, rows);
			toLoc = Torus.Adjust(toLoc, cols, rows);
			int height2 = rows / 2;
			int width2 = cols / 2;

			Directions dir = Directions.Unknown;
			if (fromLoc.Row < toLoc.Row)
			{
				dir = toLoc.Row - fromLoc.Row >= height2 ? Directions.North : Directions.South;
			}
			if (toLoc.Row < fromLoc.Row)
			{
				dir = fromLoc.Row - toLoc.Row >= height2 ? Directions.South : Directions.North;
			}
			if (fromLoc.Col < toLoc.Col)
			{
				dir = toLoc.Col - fromLoc.Col >= width2 ? Directions.West : Directions.East;
			}
			if (toLoc.Col < fromLoc.Col)
			{
				dir = fromLoc.Col - toLoc.Col >= width2 ? Directions.East : Directions.West;
			}

			/*List<Directions> dir = new List<Directions>(2);//'N', 'E', 'S', 'W'
			if (fromLoc.Row < toLoc.Row)
			{
				if (toLoc.Row - fromLoc.Row >= height2) dir.Add(Directions.North);
				if (toLoc.Row - fromLoc.Row <= height2) dir.Add(Directions.South);
			}
			if (toLoc.Row < fromLoc.Row)
			{
				if (fromLoc.Row - toLoc.Row >= height2) dir.Add(Directions.South);
				if (fromLoc.Row - toLoc.Row <= height2) dir.Add(Directions.North);
			}
			if (fromLoc.Col < toLoc.Col)
			{
				if (toLoc.Col - fromLoc.Col >= width2) dir.Add(Directions.West);
				if (toLoc.Col - fromLoc.Col <= width2) dir.Add(Directions.East);
			}
			if (toLoc.Col < fromLoc.Col)
			{
				if (fromLoc.Col - toLoc.Col >= width2) dir.Add(Directions.East);
				if (fromLoc.Col - toLoc.Col <= width2) dir.Add(Directions.West);
			}
#if LOG
			if (dir.Count == 0)
				Logger.Log("Не удалось найти направление от " + fromLoc + " к " + toLoc);
#endif
			 */
			return dir;
		}

		public void UpdateVisionInfo()
		{
			Queue<Location> locQueue = new Queue<Location>(Map.Cols * Map.Rows);
			Location nLoc;

			foreach (Location antLocation in MyAnts)
			{
				locQueue.Enqueue(antLocation);

				bool[,] visited = new bool[Map.Cols, Map.Rows];

				while (locQueue.Count > 0)
				{
					Location curLoc = locQueue.Dequeue();

					foreach (Directions dir in
						Enum.GetValues(typeof (Directions)).Cast<Directions>().Where(dir => dir != Directions.Unknown))
					{
						nLoc = GetNewLocation(curLoc, dir);

						if (!visited[nLoc.Col, nLoc.Row] &&
							Map.Distance2(antLocation, nLoc) <= Config.GameSettings.ViewRadius2)
						{
							Map.SetLastSeen(nLoc.Col, nLoc.Row, TurnNumber);
							locQueue.Enqueue(nLoc);
						}
						visited[nLoc.Col, nLoc.Row] = true;
					}

					/*for (int d = 0; d < NDIRECTIONS; d++)
					{
						nLoc = GetNewLocation(curLoc, d);

						if (!visited[nLoc.Col, nLoc.Row] && 
							Map.Distance2(antLocation, nLoc) <= Config.GameSettings.ViewRadius2)
						{
							Map.SetLastSeen(nLoc.Col, nLoc.Row, TurnNumber);
							locQueue.Enqueue(nLoc);
						}
						visited[nLoc.Col, nLoc.Row] = true;
					}*/
				}
			}
		}

		public Step MakeStep(Step step)
		{
			if (Map.Get(step.Location.Col, step.Location.Row).Ant != MY_ANT)
			{
				Logger.Log("No my ant on map[" + step.Location.Col + "," + step.Location.Row + "]!");
				return null;
			}
			Location newLoc = GetNewLocation(step.Location, step.Direction);

			Map.SetAnt(step.Location.Col, step.Location.Row, NO_ANT);
			Map.SetAnt(newLoc.Col, newLoc.Row, MY_ANT);

			MyAnts.Remove(step.Location);
			MyAnts.Add(newLoc);

			return step;
		}

		public void Update(DataSet dataSet)
		{
			Reset();

			TurnNumber = dataSet.TurnNumber;

			foreach (Location location in dataSet.Water)
				SetWater(location.Col, location.Row);

			/*foreach (Location location in dataSet.RemovedFood)
				DeleteFood(location.Col, location.Row);*/

			foreach (Pair<Location, int> pair in dataSet.Ants)
				SetAnt(pair.First.Col, pair.First.Row, pair.Second);

			UpdateVisionInfo();

			UpdateFood(dataSet.Food);
		}

		public void UpdateFood(List<Location> currentVisibleFood)
		{
			for (int i = Food.Count - 1; i >= 0; i--)
			{
				Location food = Food.Get(i);
				if (Map.Get(food).LastSeen == TurnNumber && !currentVisibleFood.Contains(food))
					DeleteFood(food.X, food.Y);
			}
			
			foreach (Location location in currentVisibleFood)
				SetFood(location.Col, location.Row);
		}

		public void SetAnt(int col, int row, int owner)
		{
			Location location = new Location(col, row);
			if (owner == MY_ANT)
			{
				if (Map.Get(col, row).Ant != MY_ANT)
					MyAnts.Add(location);
			}
			else
			{
				if (Map.Get(col, row).Ant <= MY_ANT)
					Enemies.Add(location);
			}
			Map.SetAnt(col, row, owner);
		}

		public void DeleteAnt(int col, int row)
		{
			MyAnts.Remove(new Location(col, row));
			Enemies.Remove(new Location(col, row));
			Map.SetAnt(col, row, NO_ANT);
		}

		public void SetWater(int col, int row)
		{
			Water.Add(new Location(col, row));
			Map.SetWater(col, row);
			Router.SetWater(col, row);
		}

		public void SetFood(int col, int row)
		{
			Food.Add(new Location(col, row));
			Map.SetFood(col, row, true);
		}

		public void DeleteFood(int col, int row)
		{
			Food.Remove(new Location(col, row));
			Map.SetFood(col, row, false);
		}

		public void SetLastSeen(int col, int row)
		{
			Map.SetLastSeen(col, row, TurnNumber);
		}

		public void Reset()
		{
			MyAnts.Clear();
			Enemies.Clear();
			//Food.Clear();
			Map.Reset();
		}

		public override string ToString()
		{
			string result = Config.GameSettings + "\n";
			for (int i = 0; i < Map.Rows; i++)
			{
				for (int j = 0; j < Map.Cols; j++)
					result += Map.Get(j, i).GetChar(TurnNumber);
				result += '\n';
			}
			return result;
		}

		public void MapFromString(string mapString)
		{
			if (mapString.Length != Map.Cols * Map.Rows)
				throw new ArgumentException("String size doesn't match map size!");

			for (int i = 0; i < mapString.Length; i++)
			{
				int x = i % Map.Cols;
				int y = i / Map.Cols;

				Square square = new Square{Ant = NO_ANT, LastSeen = mapString[i] == Square.UNKNOWN ? 0 : 1};
				Map.Set(x, y, square);

				if (mapString[i] == Square.WATER) SetWater(x, y);
				if (mapString[i] == Square.FOOD) SetFood(x, y);
	
				if (mapString[i] >= Square.MY_ANT && mapString[i] <= Square.MY_ANT + 25)
					SetAnt(x, y, MY_ANT + (mapString[i] - Square.MY_ANT));
			}
		}
	}
}
